/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;

using Microsoft.VisualStudio.Modeling;

namespace DslEditorPowerToy.Languages
{
    /// <summary>
    /// Helper class for manipulation and discovery of domain model
    /// </summary>
    public static class MetaModelHelper
    {
        #region Public Methods

        /// <summary>
        /// Returns the parent relationship embedding this element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        static public ElementLink GetEmbeddingParentLink(ModelElement element)
        {
            return DomainClassInfo.FindEmbeddingElementLink(element);
        }
        /// <summary>
        /// Returns the parent embedding this element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        static public ModelElement GetEmbeddingParentElement(ModelElement element)
        {
            return DomainClassInfo.FindEmbeddingElement(element);
        }        
        /// <summary>
        /// Walks ancestry till top element
        /// </summary>
        /// <param name="relationship"></param>
        /// <returns></returns>
        static public ModelElement GetRootElement(ElementLink relationship)
        {
            return GetRootElement(relationship as ModelElement);
        }
        /// <summary>
        /// Walks ancestry till top element
        /// </summary>
        /// <param name="relationship"></param>
        /// <returns></returns>
        static public ModelElement GetRootElement(ModelElement element)
        {
            if (null == element)
                return null;

            ModelElement parentElement = null;

            if (element is ElementLink)
                parentElement = GetEmbeddingParentElement(element as ElementLink);
            else
            {
                //Get a parent element (anywhere in graph)
                parentElement = GetEmbeddingParentElement(element);
                if (null == parentElement)
                    return element; //Must be root already (or orphan element)
            }

            //Recurse ancestors till reaching the top
            do
            {
                //Check if top of the graph
                ModelElement ancestor = GetEmbeddingParentElement(parentElement);
                if (null == ancestor)
                    return parentElement;
                else
                    parentElement = GetEmbeddingParentElement(parentElement);
            } while (null != parentElement);

            return null;
        }
        /// <summary>
        /// Returns if element id root model element
        /// </summary>
        /// <returns></returns>
        static public bool IsRootElement(ModelElement element)
        {
            if (null == element)
                return false;

            if (element is ElementLink)
                return false;

            //Get a parent element (anywhere in graph)
            ModelElement parentElement = GetEmbeddingParentElement(element);
            if (null == parentElement)
                return true; //Must be root already (or orphan element)

            return false;
        }
        /// <summary>
        /// Returns the name for the element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="domainClassInfo"></param>
        /// <returns></returns>
        static public string GetName(ModelElement element)
        {
            if (true == DomainClassInfo.HasNameProperty(element))
                return DomainClassInfo.GetName(element);
            else
            {
                //Return the default description for the name
                return element.ToString();
            }
        }
        /// <summary>
        /// Returns the instances of all all embedding relationships
        /// </summary>
        /// <returns></returns>
        static public ReadOnlyCollection<ElementLink> GetAllEmbeddedRelationships(ModelElement parentElement)
        {
            List<ElementLink> links = new List<ElementLink>();

            DomainClassInfo parentClassInfo = parentElement.GetDomainClass();

            //Get the relationship instances for all roles played
            ReadOnlyCollection<DomainRoleInfo> rolesInfo = GetAllEmbeddingRoleInfo(parentClassInfo);
            foreach (DomainRoleInfo roleInfo in rolesInfo)
            {
                //Get linked element instances for this role
                LinkedElementCollection<ModelElement> elementList = GetRoleChildCollection(roleInfo, parentElement);

                //Get the instance of the relationship
                ReadOnlyCollection<ElementLink> relationships = elementList.SourceDomainRole.GetElementLinks(parentElement);
                links.AddRange(relationships);
            }

            return new ReadOnlyCollection<ElementLink>(links);
        }
        /// <summary>
        /// Returns the list of children element instances of the first embedding relationship
        /// </summary>
        /// <returns></returns>
        static public LinkedElementCollection<ModelElement> GetFirstEmbeddedRelationshipChildren(ModelElement parentElement)
        {
            DomainClassInfo parentClassInfo = parentElement.GetDomainClass();

            //Get first embedded role
            DomainRoleInfo roleInfo = GetFirstEmbeddedChildRoleInfo(parentClassInfo);
            if (null == roleInfo)
                return null;

            return GetRoleChildCollection(roleInfo, parentElement);
        }
        /// <summary>
        /// Adds a new child element to first embedded child role
        /// </summary>
        /// <param name="modelElement"></param>
        /// <param name="element"></param>
        static public ModelElement AddNewChildToFirstEmbeddingParent(ModelElement parentElement, DomainClassInfo childClassInfo)
        {
            if ((null == parentElement)
                || (null == childClassInfo))
                return null;

            //Get first embedded role list
            IList childElements = GetFirstEmbeddedRelationshipChildren(parentElement);
            if (null == childElements)
                return null;

            //Add element to parent child list
            using (Transaction transaction = parentElement.Store.TransactionManager.BeginTransaction(string.Format("Add New {0}", childClassInfo.DisplayName)))
            {
                //Create element
                ModelElement element = parentElement.Store.ElementFactory.CreateElement(childClassInfo);
                if (null == element)
                    return null;

                //Add element to parent role
                childElements.Add(element);

                //Assign it a unique name
                if (true == DomainClassInfo.HasNameProperty(element))
                    DomainClassInfo.SetUniqueName(element);

                transaction.Commit();

                return element;
            }
        }
        /// <summary>
        /// Removes the child element from first embedding role
        /// </summary>
        /// <param name="modelElement"></param>
        /// <param name="element"></param>
        static public void RemoveChildFromEmbeddingParent(ModelElement element)
        {
            if (null == element)
                return;

            DomainClassInfo elementClassInfo = element.GetDomainClass();

            ModelElement parentElement = GetEmbeddingParentElement(element);
            if (null == parentElement)
                return;

            DomainClassInfo parentClassInfo = parentElement.GetDomainClass();

            //Get all embedding roles on parent
            IList childElements = null;
            ReadOnlyCollection<DomainRoleInfo> rolesInfo = GetAllEmbeddingRoleInfo(parentClassInfo);
            foreach (DomainRoleInfo roleInfo in rolesInfo)
            {
                //Find one for this relationship
                if (roleInfo.OppositeDomainRole.RolePlayer == elementClassInfo)
                {
                    childElements = GetRoleChildCollection(roleInfo, parentElement);
                    break;
                }
            }

            //Remove element from parent child list
            using (Transaction transaction = parentElement.Store.TransactionManager.BeginTransaction("Delete"))
            {
                //Remove element from parent role
                childElements.Remove(element);

                transaction.Commit();
            }
        }

        
        /// <summary>
        /// Returns the embedding roles, where the parent embeds child elements
        /// </summary>
        /// <param name="parentElement"></param>
        /// <param name="parentClassInfo"></param>
        /// <param name="propName"></param>
        public static ReadOnlyCollection<DomainRoleInfo> GetAllEmbeddingRoleInfo(DomainClassInfo parentClassInfo)
        {
            List<DomainRoleInfo> roles = new List<DomainRoleInfo>();
            foreach (DomainRoleInfo roleInfo in parentClassInfo.LocalDomainRolesPlayed)
            {
                //Ensure its embedding
                if (true == roleInfo.IsEmbedding)
                    roles.Add(roleInfo);
            }

            return new ReadOnlyCollection<DomainRoleInfo>(roles);
        }
        /// <summary>
        /// Returns the first embedding role, where the parent embeds child elements
        /// </summary>
        /// <param name="parentClassInfo"></param>
        public static DomainRoleInfo GetFirstEmbeddedChildRoleInfo(DomainClassInfo parentClassInfo)
        {
            ReadOnlyCollection<DomainRoleInfo> roles = GetAllEmbeddingRoleInfo(parentClassInfo);
            if ((null != roles)
                && (roles.Count > 0))
                return roles[0];
            else
                return null;
        }
        /// <summary>
        /// Returns the child element info of the first embedding relationship
        /// </summary>
        /// <returns></returns>
        static public DomainClassInfo GetFirstEmbeddedChildInfo(ModelElement parentElement)
        {
            DomainClassInfo parentClassInfo = parentElement.GetDomainClass();

            //Get first embedded role
            DomainRoleInfo roleInfo = GetFirstEmbeddedChildRoleInfo(parentClassInfo);
            if (null == roleInfo)
                return null;

            //Get the right hand (child) role player of this roles relationship
            return GetOppositeRolePlayerInfo(roleInfo);
        }
        /// <summary>
        /// Returns the first embedded relationship, where the parent embeds child elements
        /// </summary>
        /// <param name="parentElement"></param>
        /// <param name="parentClassInfo"></param>
        /// <param name="propName"></param>
        public static DomainRelationshipInfo GetFirstEmbeddedRelationshipInfo(DomainClassInfo parentClassInfo)
        {
            DomainRoleInfo roleInfo = GetFirstEmbeddedChildRoleInfo(parentClassInfo);
            if (null != roleInfo)
                return roleInfo.DomainRelationship;
            else
                return null;
        }
        /// <summary>
        /// Returns the right hand (child) role player of the relationship
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static DomainClassInfo GetRightHandRolePlayerInfo(ElementLink link)
        {
            if (null == link)
                return null;

            ReadOnlyCollection<DomainRoleInfo> roleInfo = link.GetDomainRelationship().DomainRoles;
            if ((null == roleInfo)
                || (roleInfo.Count < 2))
                return null;

            //Return right hand role player
            return roleInfo[1].RolePlayer;
        }
        /// <summary>
        /// Returns the role on the other side of the relationship
        /// </summary>
        /// <param name="roleInfo"></param>
        /// <returns></returns>
        public static DomainClassInfo GetOppositeRolePlayerInfo(DomainRoleInfo roleInfo)
        {
            return roleInfo.OppositeDomainRole.RolePlayer;
        }
        /// <summary>
        /// Returns the left hand role (parent) of a relationship
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static ModelElement GetEmbeddingParentElement(ElementLink link)
        {
            return link.LinkedElements[0];
        }
        /// <summary>
        /// Returns the right hand role (child) of a relationship
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static ModelElement GetEmbeddedChildElement(ElementLink link)
        {
            return link.LinkedElements[1];
        }

        /// <summary>
        /// Returns a list of domain property info for the specified element
        /// </summary>
        /// <param name="modelElement"></param>
        /// <returns></returns>
        static public ReadOnlyCollection<DomainPropertyInfo> GetAllPropertyInfo(ModelElement element)
        {
            return GetAllPropertyInfo(element.GetDomainClass());
        }
        /// <summary>
        /// Returns list of domain properties for domain class
        /// </summary>
        /// <param name="classInfo"></param>
        /// <returns></returns>
        static public ReadOnlyCollection<DomainPropertyInfo> GetAllPropertyInfo(DomainClassInfo classInfo)
        {
            if (null == classInfo)
                return null;

            return classInfo.AllDomainProperties;
        }

        #endregion

        #region Private Methods
        /// <summary>
        /// Returns the collection of children element instances of the specified embedding role
        /// </summary>
        /// <returns></returns>
        static private LinkedElementCollection<ModelElement> GetRoleChildCollection(DomainRoleInfo roleInfo, ModelElement parentElement)
        {
            if (null == roleInfo)
                return null;

            return roleInfo.GetLinkedElements(parentElement);
        }
        #endregion
    }
}
